/*
 * Copyright (c) 2006, 2007 Sun Microsystems, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

package org.sunspotworld.test;

import com.sun.spot.peripheral.IPowerController;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.peripheral.external.ExternalBoard;
import com.sun.spot.sensorboard.peripheral.ILightSensor;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.sensorboard.peripheral.LEDColor;
import com.sun.spot.sensorboard.peripheral.ISwitch;
import com.sun.spot.sensorboard.peripheral.ISwitch;
import com.sun.spot.sensorboard.io.IScalarInput;
import com.sun.spot.sensorboard.peripheral.IAccelerometer3D;
import com.sun.spot.sensorboard.peripheral.ITemperatureInput;
import com.sun.spot.peripheral.IUSBPowerDaemon;
import com.sun.spot.peripheral.PeripheralChipSelect;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Date;
import com.sun.spot.util.Properties;


import com.sun.spot.util.Utils;
import java.io.IOException;

import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;


/**
 * This is a startup application that runs on the Sun SPOT device.
 * It checks various portions of the eSPOT for functionality
 * SW2 (right switch with LEDs at top) selects the tests.
 * SW1 (left switch with LEDs at top) selects modes from within each test
 * When SW2 is pushed the corresponding white LED will momentarily flash
 * to identify which number test is run, LED1 is test 1, LED2 is test2, etc.
 * Test 1: power measurements (SW1 to cycle through measurements)
 * 	Current Measurement: Red is discharge, Green is charge
 *    External or USB Voltage Measurement: Cyan (up to LED7 is ok)
 *    Battery Voltage Measurement: Blue (up to LED 6 is charged battery)
 *    Vcc Voltage Measurement: Yellow (up to LED4 and dim LED5 is ok)
 *    Vcore Voltage Measurement: Orange (up to LED3 is ok)
 *    Main board temperature: (dim red for rev 6 or greater)
 * Test 2: Light level bargraph (grey)
 * Test 3: Temperature bargraph (dim red) (midscale is 72 deg F)
 * Test 4: Accelerometer bargraph (SW1 to cycle through axis)
 *    Accelerometer X axis: Red
 *    Accelerometer Y axis: Green
 *    Accelerometer Z axis: Blue
 * Test 5: LED test
 *    All Red LEDs
 *    All Green LEDs
 *    All Blue LEDs
 *    All White LEDs
 * Test 6: Power Fault/USB test (SW1 to switch between Power Fault and USB)
 *    Power Fault: Green OK, Red Bad
 *		LED1: Battery out of range
 *		LED2: USB Voltage out of range
 *		LED3: External Voltage out of range
 *		LED4: Vcc out of range
 *		LED5: Vcore out of range
 *		LED6: Power up took to long
 *		LED7: Current Overloaded
 *		LED8: RTC failed to start
 * USB green if true, off if false
 *      LED1: USB Power detected 
 * 		LED2: USB enumerated 
 * 		LED3: USB in use - not guaranteed on all OS
 * @author Bob
 * added temperature sensor output, USB testing
 */

public class SpotCheck extends MIDlet {
    private static final LEDColor WHITE = new LEDColor(255,255,255);
    private static final LEDColor GREY =  new LEDColor(127,127,127);
    private static final LEDColor ORANGE = new LEDColor(255,120,0);
    private static final LEDColor DIMRED = new LEDColor(32,0,0);
    
    private ITriColorLED[] leds;
    private ISwitch sw1;
    private ISwitch sw2;
    private IPowerController avr;
    private EDemoBoard eDemo;
    private ILightSensor lightSensor;
    private IAccelerometer3D accelerometer;
    private ITemperatureInput thermometer;
    private IUSBPowerDaemon usb;
    
    private int lightLevel;
    private double temperature;
    private String mainTemperature;
    private int vcc;
    private int vcore;
    private int vbatt;
    private int vext;
    private int vusb;
    private int icharge;
    private int idischarge;
    private long time;
	private int startupTime;
	private int powerFaults;
	
    private int State1 = 0;
    private int State2 = 0;
    private int acc;
    boolean SW2Changed = true;
    private int hwrev;
 


    private void run() throws IOException {
    	avr = Spot.getInstance().getPowerController();
        System.out.println("Power Controller = " + avr.getRevision());
        boolean has_eDemo = hasThisExternalBoard("EDEMO");
        printBoardProperties();
        usb = Spot.getInstance().getUsbPowerDaemon();
        if (has_eDemo) {
            eDemo = EDemoBoard.getInstance();
            lightSensor = eDemo.getLightSensor();
            accelerometer = eDemo.getAccelerometer();
            thermometer = eDemo.getADCTemperature();
            sw1 = eDemo.getSwitches()[0];
            sw2 = eDemo.getSwitches()[1];
            startSW1WatchThread();
            startSW2WatchThread();
            
            int[] firmwareVersion = eDemo.getFirmwareVersion();
            System.out.println("eDemo board revision: "+firmwareVersion[0]+"."+firmwareVersion[1]);
            leds = eDemo.getLEDs();
        } else {
            System.out.println("No eDEMO board found");
        }
        System.out.println("IEEE_ADDR = "+System.getProperty("IEEE_ADDRESS"));
  		hwrev = Spot.getInstance().getHardwareType();       
        System.out.println("Hardware Revision = "+hwrev);
        System.out.println("USB " + (usb.isUsbPowered() ? "": "un") + "powered, " + (usb.isUsbEnumerated()?"":"not ") + "enumerated, " + (usb.isUsbInUse()?"":"not ") + "in use");
        int status = avr.getStatus();
        System.out.println("Status was 0x" + Integer.toHexString(status) + ((status == 0) ? "" : statusAsString(status)));
        
        boolean again = true;
        while(again) {
            lightLevel = 0;
            vcc = avr.getVcc();
            vcore = avr.getVcore();
            vbatt =  avr.getVbatt();
            vext = avr.getVext();
            vusb = avr.getVusb();
            icharge = avr.getIcharge();
            idischarge = avr.getIdischarge();
            try {
            	mainTemperature = Double.toString(avr.getTemperature());
				mainTemperature = mainTemperature.substring(0,mainTemperature.indexOf('.')+2);
            } catch (IllegalStateException e) {
            	mainTemperature = null;
            }
			startupTime = avr.getStartupTime();
			powerFaults = avr.getPowerStatus();
 
            if (has_eDemo) {
                if (SW2Changed) {
                    for (int i = 0; i < 8; i++) leds[i].setOff();
                    leds[State2].setColor(WHITE);
                    leds[State2].setOn();
                    Utils.sleep(500);
                    leds[State2].setOff();
                    SW2Changed = false;
                }
                switch(State2) {
                    case 0: // cycle through power measurements on SPOT and report in bargraph
                        switch(State1) {
                            case 0:
                                if (icharge > idischarge) {
                                    ledBarGraph(icharge, 500, LEDColor.GREEN); //show charge current level in green
                                } else {
                                    ledBarGraph(idischarge, 500, LEDColor.RED); //show discharge in level in red
                                }
                                break;
                            case 1:
                                if (vext > vusb) {
                                    ledBarGraph(vext, 6000, LEDColor.CYAN); //show External Voltage in CYAN
                                 } else {
                                    ledBarGraph(vusb, 6000, LEDColor.CYAN); //show USB Voltage in CYAN
                                }
                                break;
                            case 2:
                                ledBarGraph(vbatt, 6000, LEDColor.BLUE); //show battery voltage in blue
                                 break;
                            case 3:
                                ledBarGraph(vcc, 6000, LEDColor.YELLOW); //show Vcc in yellow
                                break;
                            case 4:
                                ledBarGraph(vcore, 6000, ORANGE); //show vcore level in orange
                                break;
                            case 5: 
                            	try {
                         			ledBarGraph(((int)avr.getTemperature())+20, 70, DIMRED); //show temperature level in WHITE
                        		} catch (IllegalStateException e) {
                        			State1 = 0;
                        		}
                        		break;
                             case 6:
                                State1 = 0;
                                break;
                        }
                        printPCTRL();
                        Utils.sleep(100);
                        break;
                    case 1: // report light level from light sensor on bargraph
                        lightLevel = lightSensor.getValue();       // Ranges from 0 - 740
                        ledBarGraph(lightLevel, 740, GREY); //show light level in Green
                        System.out.println("Light level = " + lightLevel);
                        Utils.sleep(100);
                        break;
                    case 2: // report temperature on bargraph
                        temperature = thermometer.getFahrenheit();       // Ranges from 50 - 120
                        ledBarGraph(((int)temperature)-50, 70, DIMRED); //show temperature level in WHITE
                        System.out.println("Temperature: " + temperature+" deg. F");
                        Utils.sleep(100);
                        break;
                    case 3: // cycle through axis of accelerometer and report on bargraph
                        try {
                            switch(State1) {
                                case 0:
                                    acc = (int)(accelerometer.getAccelX() * 10);
                                    ledBarGraph( acc + 20, 40, LEDColor.RED);
                                    System.out.println("Accelerometer X axis = " + acc);
                                    break;
                                case 1:
                                    acc = (int)(accelerometer.getAccelY() * 10);
                                    ledBarGraph( acc + 20, 40, LEDColor.GREEN);
                                    System.out.println("Accelerometer Y axis = " + acc);
                                    break;
                                case 2:
                                    acc = (int)(accelerometer.getAccelZ() * 10);
                                    ledBarGraph( acc + 20, 40, LEDColor.BLUE);
                                    System.out.println("Accelerometer Z axis = " + acc);
                                    break;
                                case 3:
                                    State1 = 0;
                                    break;
                            }
                            Utils.sleep(50);
                        } catch (IOException e) { }
                        break;
                    case 4: // cycle through the red, green and blue LEDs.
                        int k;
                        for(int m = 0; m <= 3; m++) {
                        	for(int j = 0; j < 1024; j++) {
                          	  if (j > 512) k = 1024 - j; else k = j;
                          	  switch(m) {
                           	  	case 0:
                                    ledBarGraph( k, 512, LEDColor.RED);
                                    break;
                               	case 1:
                                    ledBarGraph( k, 512, LEDColor.GREEN);
                                    break;
                                case 2:
                                    ledBarGraph( k, 512, LEDColor.BLUE);
                                    break;
                                case 3:
                                    ledBarGraph( k, 512, WHITE);
                                    break;
                             	}
                        	}
                        }                        
                        break;
                    case 5:
                    	switch(State1) {
                        	case 0:
                    			powerFaults = avr.getPowerStatus();
                        		for (int i = 0; i < 8; i++) {
                        			if (((powerFaults>>i)&1) == 1) leds[i].setColor(LEDColor.RED);
                            		else leds[i].setColor(LEDColor.GREEN);
                            		leds[i].setOn();
                        		}
								Utils.sleep(50);
                    			break;
                    		case 1: 
                            	for (int i = 3; i < 8; i++) leds[i].setOff();
                        		if (usb.isUsbPowered()) {
                         			leds[0].setColor(LEDColor.GREEN);
                            		leds[0].setOn();
                            	} else leds[0].setOff();
                         		if (usb.isUsbEnumerated()) {
                         			leds[1].setColor(LEDColor.GREEN);
                            		leds[1].setOn();
                            	} else leds[1].setOff();
                         		if (usb.isUsbInUse()) {
                         			leds[2].setColor(LEDColor.GREEN);
                            		leds[2].setOn();
                             	} else leds[2].setOff();
                             	break;
                            case 2:
                            	State1 = 0;
                            	break;
                            }
							Utils.sleep(50);
                    	break;
                    }
            } else {
            	printPCTRL();
				Utils.sleep(2000);
            }
            
        }
        
    }
    
    
    /**
     * Generate a separate thread to watch change in switch 1
     * Invokes open and closed methods on switch state change
     * taken from the bounce demo
     */
    
    private void startSW1WatchThread(){
        Runnable r = new Runnable(){
            public void run(){
                while(true){
                    sw1.waitForChange();
                    if(sw1.isClosed()){
                        sw1Closed();
                    } else {
                        sw1Opened();
                    }
                }
            }
        };
        (new Thread(r)).start();
    }
    
    /**
     * Generate a separate thread to watch change in switch 2
     * Invokes open and closed methods on switch state change
     * taken from the bounce demo
     */
    private void startSW2WatchThread(){
        Runnable r = new Runnable(){
            public void run(){
                while(true){
                    sw2.waitForChange();
                    if(sw2.isClosed()){
                        sw2Closed();
                    } else {
                        sw2Opened();
                    }
                }
            }
        };
        (new Thread(r)).start();
    }
    
    /**
     * Method called when switch 1 closes
     */
    private void sw1Closed() {
        State1++;
    }
    /**
     * Method called when switch 2 closes
     */
    private synchronized void sw2Closed() {
        State2++;
        State1 = 0;
        SW2Changed = true;
        if (State2 > 5) State2 = 0;
    }
    /**
     * Method called when switch 2 opens
     * placeholder
     */
    private void sw1Opened() {
    }
    /**
     * Method called when switch 2 opens
     * placeholder
     */
    private void sw2Opened() {
    }
    
    /**
     * Answer a string interpretation of power fault bit pattern
     * @param fault 8 bits power fault value from power controller
     */
    private String faultAsString(int fault) {
        String str = "";
        if ((fault&1) == 1) str += ", Battery out of range";
        if ((fault&2) == 2) str += ", USB Voltage out of range";
        if ((fault&4) == 4) str += ", External Voltage out of range";
        if ((fault&8) == 8) str += ", Vcc out of range";
        if ((fault&16) == 16) str += ", Vcore out of range";
        if ((fault&32) == 32) str += ", Power up took to long";
        if ((fault&64) == 64) str += ", Current Overloaded";
        if ((fault&128) == 128) str += ", RTC failed to start";
        if (str == "") str = "No Power Fault";
        return str;
    }
    
    /**
     * Answer a string interpretation of status bit pattern from power controller
     * @param status 8 bits status value from power controller
     */
    private String statusAsString(int status) {
        String str = "";
        if ((status&1) != 0) str += ", Cold Boot";
        if ((status&2) != 0) str += ", Button Event";
        if ((status&4) != 0) str += ", Alarm Event";
        if ((status&8) != 0) str += ", Sensor Event";
        if ((status&16) != 0) str += ", New Battery Event";
        if ((status&32) != 0) str += ", Sleep Event";
        if ((status&128) != 0) str += ", External Power Event";
        return str;
    }
    
    /**
     * Answer a string constructed to display readings from the power controller
     * and testing if the are above or below acceptible margin limits. Indicates if it is within
     * limits, or if it went above or below the limit.
     * @param value data from the ADC channel corresponding to what param
     * @param low the acceptable lower limit in same units as value
     * @param high the acceptable upper limit in same units as value
     * @param units text string of the units of value ie ma, mV etc.
     * @param what text string of what is being measured ie Vbattery, Current Discharge, etc
     */
    private String levelsAsString(int value, int low, int high, String units, String what) {
        String str = what + ": " + value + units;
        if (value < low) { str += " below margin("+low+units+")"; } else if (value > high) { str += " above margin("+high+units+")"; } else str += " ok";
        return str;
    }
    
    /**
     * Generate a bar graph ont the LEDs to represent the value of num on a
     * scale from zero to limit in color
     * @param num The number to graph
     * @param limit The high end of the range.  The low end is assumed to be zero
     * @param color the color of the LEDs for the bar graph
     */
    private void ledBarGraph( int num, int limit, LEDColor color ) {
        if ( num > limit ) num = limit;
        if ( num < 0 ) num = 0;
        int pos = (num * 8)/limit;
        int frac = ((num - ((pos*limit)/8)) * 2048)/limit;
        LEDColor rc = new LEDColor((color.red()*frac)/256,(color.green()*frac)/256,(color.blue()*frac)/256);
        
        for (int i = 0; i < 8; i++) {
            if (i < pos) {
                leds[i].setColor(color);
                leds[i].setOn();
            } else if (i == pos) {
                leds[i].setColor(rc);
                leds[i].setOn();
            } else {
                leds[i].setOff();
            }
        }
    }
    
    private boolean hasThisExternalBoard(String board) {
    	Hashtable map = Spot.getInstance().getExternalBoardMap();
    	for(Enumeration e = map.elements(); e.hasMoreElements(); ) {
    		Properties p = (Properties) e.nextElement();
    		String id = (String) p.get(ExternalBoard.ID_PROPERTY_NAME);
    		if (id.startsWith(board)) return true;
    	}
    	return false;
    }
    
    private void printBoardProperties() {
    	Hashtable map = Spot.getInstance().getExternalBoardMap();
    	for(Enumeration e = map.keys(); e.hasMoreElements(); ) {
			PeripheralChipSelect key = (PeripheralChipSelect) e.nextElement();
			Properties p = (Properties) map.get(key);
			System.out.println("Slot " + key.toString() + " Properties:");
			for(Enumeration pe = p.propertyNames(); pe.hasMoreElements(); ) {
	   		 	String name = (String) pe.nextElement();
	    		String val = (String) p.get(name);
	    		if (val.length() > 40) {
                	val = val.substring(0, 37) + "...";
	    		}
	    		System.out.println("\t " + name + ": " + val);
	    	}
	    }
	}


    private void printPCTRL() {
          System.out.println("\nTime = "+ new Date(avr.getTime()));
          System.out.println(levelsAsString(vcc, 2850, 3150, "mV", "Vcc"));
          System.out.println(levelsAsString(vcore, 1710, 1890, "mV", "Vcore"));
          System.out.println(levelsAsString(vbatt, 2945, 4935, "mV", "Vbattery"));
          if (vext < 3000) {
              System.out.println("No external voltage ("+vext+"mV)");
          } else {
              System.out.println(levelsAsString(vext, 4500, 5500, "mV", "Vexternal"));
          }
          if (vusb < 3000) {
              System.out.println("No USB voltage ("+vusb+"mV)");
          } else {
              System.out.println(levelsAsString(vusb, 4500, 5500, "mV", "Vusb"));
          }
          System.out.println(levelsAsString(icharge, 0, 550, "ma", "Charge Current"));
          System.out.println(levelsAsString(idischarge, 0, 500, "ma", "Discharge Current"));
          if (mainTemperature != null) System.out.println(mainTemperature + "C Temperature");
          System.out.println("Power Start Time = "+startupTime+"us");
          if (powerFaults != 0) {
          	System.out.println("Power Fault=0x"+Integer.toHexString(powerFaults));
          	System.out.println(faultAsString(powerFaults));
          }
    }

	
    /**
     * The rest is boiler plate code, for Java ME compliance
     *
     * startApp() is the MIDlet call that starts the application.
     */
    protected void startApp() throws MIDletStateChangeException {
        try {
            run();
        } catch (IOException ex) { //A problem in reading the sensors.
            ex.printStackTrace();
        }
    }
    
    /**
     * This will never be called by the Squawk VM.
     */
    protected void pauseApp() {
    }
    
    /**
     * Called if the MIDlet is terminated by the system.
     * I.e. if startApp throws any exception other than MIDletStateChangeException,
     * if the isolate running the MIDlet is killed with Isolate.exit(), or
     * if VM.stopVM() is called.
     * 
     * It is not called if MIDlet.notifyDestroyed() was called.
     */
    protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
    }
    
}
